﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sustainalytics.Entities;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;

namespace Sustainalytics.DataLayer.Tests
{
    [TestClass]
    public class NewsRepositoryTests
    {
        private INewsRepository NewsRepo { get; set; }

        [TestInitialize]
        public void Initialize()
        {
            // setup repository
            this.NewsRepo = NewsRepository.GetNewsRepository(ConfigurationManager.ConnectionStrings["ClientPlatform_Test"].ConnectionString);

            NewsTestingData.CleanupRepo(this.NewsRepo);
            NewsTestingData.InitializeRepo(this.NewsRepo);
        }

        [TestCleanup]
        public void Cleanup()
        {
            NewsTestingData.CleanupRepo(this.NewsRepo);
        }

        [TestMethod]
        public void GetTop5News()
        {
            List<News> newsLst = this.NewsRepo.GetNews(
                filter: new NewsFilter(),
                skip: 0,
                take: 5,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            Assert.IsTrue(newsLst != null, "Paged list is null.");
            Assert.IsTrue(newsLst.Count == 5, "Items count are not equal.");
            Assert.IsTrue(newsLst[0].DateCreated >= newsLst[1].DateCreated, "News are not in the correct order");
            Assert.IsTrue(newsLst[1].DateCreated >= newsLst[2].DateCreated, "News are not in the correct order");
            Assert.IsTrue(newsLst[2].DateCreated >= newsLst[3].DateCreated, "News are not in the correct order");
            Assert.IsTrue(newsLst[3].DateCreated >= newsLst[4].DateCreated, "News are not in the correct order");
        }

        [TestMethod]
        public void GetNews_ItemsAreEmpty()
        {
            List<News> newsLst = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = Guid.NewGuid().ToString() },
                skip: 0,
                take: 20,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            Assert.IsTrue(newsLst != null, "Paged list is null.");
            Assert.IsTrue(newsLst.Count == 0, "Items is null");
        }

        [TestMethod]
        public void GetNews_ItemsAreCorrectAndInOrder()
        {
            string term = "UT The News";

            int skip = 0;
            int take = 20;

            List<News> newsLst = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = term },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            List<News> memNewsLst =
                NewsTestingData.NewsLst.Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant())))
                    .OrderByDescending(n => n.DateCreated)
                    .Skip(skip)
                    .Take(take)
                    .ToList();

            Assert.IsTrue(newsLst != null, "List is null");
            Assert.IsTrue(newsLst.Count == memNewsLst.Count, "Items count are not equal.");
            Assert.IsTrue(newsLst.SequenceEqual(memNewsLst, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNews_PagesDoesNotOverlap()
        {
            string term = "UT ";

            int skip1 = 0;
            int take1 = 20;
            int skip2 = 20;
            int take2 = 40;

            List<News> newsPaged1 = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = term },
                skip: skip1,
                take: take1,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            Assert.IsTrue(newsPaged1 != null, "List is null.");

            List<News> newsPaged2 = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = term },
                skip: skip2,
                take: take2,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            Assert.IsTrue(newsPaged2 != null, "List is null.");

            Assert.IsTrue(newsPaged1.Intersect(newsPaged2).Any() == false, "Pages have items in common.");
        }

        [TestMethod]
        public void GetNews_FilterNull()
        {
            this.NewsRepo.GetNews(null, 0, 20, new string[] { "DateCreated" }, false);
        }

        [TestMethod]
        public void GetNews_Filter_Term_MatchTitle()
        {
            string term = "UT The News 1";

            int skip = 0;
            int take = 20;

            // DB news
            List<News> newsLst = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = term },
                skip: 0,
                take: 20,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsLst =
                NewsTestingData.NewsLst.Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant())))
                    .OrderByDescending(n => n.DateCreated)
                    .Skip(skip)
                    .Take(take)
                    .ToList();

            Assert.IsTrue(newsLst != null, "List is null.");
            Assert.IsTrue(newsLst.Count == memNewsLst.Count, "Items count are not equal.");
            Assert.IsTrue(newsLst.SequenceEqual(memNewsLst, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNews_Filter_Term_MatchTags()
        {
            string term = "tag 2";

            int skip = 0;
            int take = 20;

            // DB news
            List<News> newsLst = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = term },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsLst = NewsTestingData.NewsLst
                .Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant())))
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsLst != null, "List is null.");
            Assert.IsTrue(newsLst.Count == memNewsLst.Count, "Items count are not equal.");
            Assert.IsTrue(newsLst.SequenceEqual(memNewsLst, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNews_Filter_Term_MatchTitleAndTags()
        {
            string term = "1";

            int skip = 0;
            int take = 20;

            // DB news
            List<News> newsPaged = this.NewsRepo.GetNews(
                filter: new NewsFilter { Term = term },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged = NewsTestingData.NewsLst
                .Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant())))
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged != null, "List is null.");
            Assert.IsTrue(newsPaged.Count == memNewsPaged.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged.SequenceEqual(memNewsPaged, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNews_Filter_Category()
        {
            NewsCategory category = NewsCategory.Corporate;

            int skip = 0;
            int take = 20;

            // DB news
            List<News> newsPaged = this.NewsRepo.GetNews(
                filter: new NewsFilter { Category = category },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged = NewsTestingData.NewsLst
                .Where(n => n.Category == category)
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged != null, "List is null.");
            Assert.IsTrue(newsPaged.Count == memNewsPaged.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged.SequenceEqual(memNewsPaged, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNews_Filter_IsPublished()
        {
            int skip = 0;
            int take = 20;

            // DB news
            List<News> newsPaged = this.NewsRepo.GetNews(
                filter: new NewsFilter { IsPublished = true },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged = NewsTestingData.NewsLst
                .Where(n => n.Status == NewsStatus.Published && n.DateCreated >= DateTime.Now.AddMonths(-3))
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged != null, "List is null.");
            Assert.IsTrue(newsPaged.Count == memNewsPaged.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged.SequenceEqual(memNewsPaged, new NewsEqualityComparer()), "Items are not the same.");

            // DB news
            List<News> newsPaged2 = this.NewsRepo.GetNews(
                filter: new NewsFilter { IsPublished = false },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged2 = NewsTestingData.NewsLst
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged2 != null, "List is null.");
            Assert.IsTrue(newsPaged2.Count == memNewsPaged2.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged2.SequenceEqual(memNewsPaged2, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNews_Filter_AvailableFilter()
        {
            NewsAvailability availableFilter = NewsAvailability.Client;

            int skip = 0;
            int take = 20;

            // DB news
            List<News> newsPaged = this.NewsRepo.GetNews(
                filter: new NewsFilter { AvailableFilter = availableFilter },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged = NewsTestingData.NewsLst
                .Where(n => n.AvailableFor == NewsAvailability.Client || n.AvailableFor == NewsAvailability.All)
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged != null, "List is null.");
            Assert.IsTrue(newsPaged.Count == memNewsPaged.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged.SequenceEqual(memNewsPaged, new NewsEqualityComparer()), "Items are not the same.");

            //NewsAvailability.Prospect
            // DB news
            List<News> newsPaged2 = this.NewsRepo.GetNews(
                filter: new NewsFilter { AvailableFilter = NewsAvailability.Prospect },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged2 = NewsTestingData.NewsLst
                .Where(n => n.AvailableFor == NewsAvailability.Prospect || n.AvailableFor == NewsAvailability.All)
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged2 != null, "List is null.");
            Assert.IsTrue(newsPaged2.Count == memNewsPaged2.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged2.SequenceEqual(memNewsPaged2, new NewsEqualityComparer()), "Items are not the same.");

            //NewsAvailability.Prospect
            // DB news
            List<News> newsPaged3 = this.NewsRepo.GetNews(
                filter: new NewsFilter { AvailableFilter = NewsAvailability.All },
                skip: skip,
                take: take,
                orderBy: new string[] { "DateCreated" },
                orderByAsc: false);

            // in mem news
            List<News> memNewsPaged3 = NewsTestingData.NewsLst
                .OrderByDescending(n => n.DateCreated)
                .Skip(skip)
                .Take(take)
                .ToList();

            Assert.IsTrue(newsPaged3 != null, "List is null.");
            Assert.IsTrue(newsPaged3.Count == memNewsPaged3.Count, "Items count are not equal.");
            Assert.IsTrue(newsPaged3.SequenceEqual(memNewsPaged3, new NewsEqualityComparer()), "Items are not the same.");
        }

        [TestMethod]
        public void GetNewsCount_NoElements()
        {
            int newsCnt = this.NewsRepo.GetNewsCount(new NewsFilter { Term = Guid.NewGuid().ToString() });

            Assert.IsTrue(newsCnt == 0, "News list is not empty.");
        }

        [TestMethod]
        public void GetNewsCount_FilterNull()
        {
            this.NewsRepo.GetNewsCount(null);
        }

        [TestMethod]
        public void GetNewsCount_Filter_Term_MatchTitle()
        {
            string term = "UT The News 1";

            // DB news
            int newsCnt = this.NewsRepo.GetNewsCount(filter: new NewsFilter { Term = term });

            // in mem news
            int memNewsCnt = NewsTestingData.NewsLst.Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant()))).Count();

            Assert.IsTrue(newsCnt > 0, "List is empty.");
            Assert.IsTrue(newsCnt == memNewsCnt, "Count is not correct");
        }

        [TestMethod]
        public void GetNewsCount_Filter_Term_MatchTags()
        {
            string term = "tag 2";

            // DB news
            int newsCnt = this.NewsRepo.GetNewsCount(filter: new NewsFilter { Term = term });

            // in mem news
            int memNewsCnt = NewsTestingData.NewsLst.Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant()))).Count();

            Assert.IsTrue(newsCnt > 0, "News list is empty.");
            Assert.IsTrue(newsCnt == memNewsCnt, "Count is not correct.");
        }

        [TestMethod]
        public void GetNewsCount_Filter_Term_MatchTitleAndTags()
        {
            string term = "1";

            // DB news
            int newsCnt = this.NewsRepo.GetNewsCount(filter: new NewsFilter { Term = term });

            // in mem news
            int memNewsCnt = NewsTestingData.NewsLst.Where(n => n.TitleLowercase.Contains(term.ToLowerInvariant()) || n.Tags.Any(t => t.NameLowercase.Contains(term.ToLowerInvariant()))).Count();

            Assert.IsTrue(newsCnt > 0, "News list is empty.");
            Assert.IsTrue(newsCnt == memNewsCnt, "Count is not correct.");
        }

        [TestMethod]
        public void GetNewsCount_Filter_Category()
        {
            NewsCategory category = NewsCategory.EsgResearch;

            // DB news
            int newsCnt = this.NewsRepo.GetNewsCount(filter: new NewsFilter { Category = category });

            // in mem news
            int memNewsCnt = NewsTestingData.NewsLst.Where(n => n.Category == category).Count();

            Assert.IsTrue(newsCnt > 0, "News list is empty.");
            Assert.IsTrue(newsCnt == memNewsCnt, "Count is not correct.");
        }

        [TestMethod]
        public void GetNews_NonExistingItem()
        {
            News news = this.NewsRepo.GetNews(Guid.Empty);

            Assert.IsNull(news, "Item exists.");
        }

        [TestMethod]
        public void GetNews_ExistingItem()
        {
            News news = this.NewsRepo.GetNews(NewsTestingData.NewsLst[0].Id);

            Assert.IsNotNull(news, "Item does not exist.");
        }

        [TestMethod]
        [ExpectedException(typeof(DataNewsIdsNullException))]
        public void GetNewsByIds_IDsNull_Exception()
        {
            this.NewsRepo.GetNewsByIds(null);
        }

        [TestMethod]
        public void GetNewsByIds_Success()
        {
            Guid[] newsIds = new Guid[] { NewsTestingData.NewsLst[0].Id, NewsTestingData.NewsLst[1].Id, NewsTestingData.NewsLst[2].Id };

            IEnumerable<News> newsLst = this.NewsRepo.GetNewsByIds(newsIds);

            int commonCount = newsLst.Select(n => n.Id).ToArray().Intersect(newsIds).Count();

            Assert.IsNotNull(newsLst, "Items is null.");
            Assert.IsTrue(commonCount == newsIds.Count() && commonCount == newsLst.Count(), "Retrieved items is not correct.");
        }

        [TestMethod]
        [ExpectedException(typeof(DataNewsNullException))]
        public void AddNews_NewsNull_Exception()
        {
            this.NewsRepo.AddNews(null);
        }

        [TestMethod]
        public void AddNews_Added()
        {
            News news = new News
            {
                Id = Guid.NewGuid(),
                AddedByUserId = Guid.NewGuid(),
                AvailableFor = NewsAvailability.Client,
                Body = "...",
                Category = NewsCategory.CorporateResponsibility,
                DateCreated = DateTime.Now,
                Region = NewsRegion.Dutch,
                Status = NewsStatus.Draft,
                Title = "...",
                UnpublishedDate = DateTime.Now.AddDays(1)
            };

            this.NewsRepo.AddNews(news);

            // search for existing news
            News addedNews = this.NewsRepo.GetNews(news.Id);

            Assert.IsNotNull(addedNews, "Item was not found.");
            Assert.IsTrue(addedNews.Id == news.Id, "Retrieved item is different.");
        }

        [TestMethod]
        [ExpectedException(typeof(DataNewsNullException))]
        public void UpdateNews_NewsNull_Exception()
        {
            this.NewsRepo.UpdateNews(null);
        }

        [TestMethod]
        public void UpdateNews_Updated()
        {
            News news = new News
            {
                Id = Guid.NewGuid(),
                AddedByUserId = Guid.NewGuid(),
                AvailableFor = NewsAvailability.Client,
                Body = "...",
                Category = NewsCategory.CorporateResponsibility,
                DateCreated = DateTime.Now,
                Region = NewsRegion.Dutch,
                Status = NewsStatus.Draft,
                Title = "...",
                UnpublishedDate = DateTime.Now.AddDays(1)
            };

            this.NewsRepo.AddNews(news);

            news.Body = "UpdateNews_Updated";

            this.NewsRepo.UpdateNews(news);

            News updatedNews = this.NewsRepo.GetNews(news.Id);

            Assert.IsNotNull(updatedNews, "Updated news does not exist.");
            Assert.IsTrue(updatedNews.Id == news.Id, "Updated news is different than requested news.");
            Assert.IsTrue(updatedNews.Body == news.Body, "News data was not updated.");
        }

        [TestMethod]
        public void DeleteNews_Success()
        {
            News news = new News
            {
                Id = Guid.NewGuid(),
                AddedByUserId = Guid.NewGuid(),
                AvailableFor = NewsAvailability.Client,
                Body = "...",
                Category = NewsCategory.CorporateResponsibility,
                DateCreated = DateTime.Now,
                Region = NewsRegion.Dutch,
                Status = NewsStatus.Draft,
                Title = "...",
                UnpublishedDate = DateTime.Now.AddDays(1)
            };

            // add news
            this.NewsRepo.AddNews(news);

            // delete news
            this.NewsRepo.DeleteNews(news.Id);

            // get deleted news
            News deletedNews = this.NewsRepo.GetNews(news.Id);

            Assert.IsNull(deletedNews, "Deleted item still exists.");
        }

        [TestMethod]
        public void DeleteMultipleNews_Success()
        {
            News news = new News
            {
                Id = Guid.NewGuid(),
                AddedByUserId = Guid.NewGuid(),
                AvailableFor = NewsAvailability.Client,
                Body = "...",
                Category = NewsCategory.CorporateResponsibility,
                DateCreated = DateTime.Now,
                Region = NewsRegion.Dutch,
                Status = NewsStatus.Draft,
                Title = "...",
                UnpublishedDate = DateTime.Now.AddDays(1)
            };

            // add news
            this.NewsRepo.AddNews(news);

            // delete news
            this.NewsRepo.DeleteMultipleNews(new Guid[] { news.Id });

            // get deleted news
            News deletedNews = this.NewsRepo.GetNews(news.Id);

            Assert.IsNull(deletedNews, "Deleted item still exists.");
        }
    }
}
